﻿using System;
using System.Text;
using SoftwareConsulting.BI11.NumericalMethods.Interfaces;
using SoftwareConsulting.BI11.NumericalMethods.MatrixAlgebra;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Optimization
{
    /// <summary>
    /// Класс поиска экстремумов функции "восхождением на гору" методом Пауэлла
    /// </summary>
    public class HillClimbingPowellAlgorithm : MultiVariableOptimizer
    {	    
        private OneVariableOptimizer _unidimensionalFinder;	    
        private VectorProjectedFunction[] _projections;//целевая функция спроектированная на независимые направления

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="function">Фукнция, требующая решения</param>
        /// <param name="pointsCreator">Фабрика точек</param>
        /// <param name="initialValue">Начальное значение</param>
        public HillClimbingPowellAlgorithm(IManyVariableFunction function, SuspiciousOptimalPointAndVectorFactory pointsCreator, double[] initialValue)
                    : base(function, pointsCreator, initialValue)
        {
        }

        /// <summary>
        /// Выравнивает по последнему направлению
        /// </summary>
        /// <param name="start"></param>
        private void AdjustLastDirection(Vector start)
        {
            try
            {
                int n = _projections.Length - 1;
                _projections[n].SetOrigin(GetResult());
                Vector newDirection = _projections[n].Origin - start;
                double norm = newDirection.GetNorm();
                if (norm > this.DesiredPrecision)
                {
                    newDirection.Scale(1 / norm);
                    _projections[n].Direction = newDirection;
                    _unidimensionalFinder.Function = _projections[n];
                    _unidimensionalFinder.InitialBestZeroApproximation = 0;
                    _unidimensionalFinder.Evaluate();
                    SetInitialValue(_projections[n].GetOriginShiftedBy(_unidimensionalFinder.BestZeroApproximation).CloneComponents());
                }
            }
            catch (BIMatrixIllegalDimensionException ex) 
            {
                Trace.StartBlock("BIMatrixIllegalDimensionException killed");
                Trace.Message("BIMatrixIllegalDimensionException catched and silently killed! Exception text:");
                Trace.Message(ex.ToString());
                Trace.EndBlock();
            };            
        }

        /// <summary>
        /// Возвращает относительную погрешность текущего результата
        /// </summary>
        /// <param name="previousIterationResult">результат предыдущей итерации</param>
        /// <returns></returns>
        private double ComputeRelativeError(double[] previousIterationResult)
        {
            double eps = 0;
            for (int i = 0; i < GetResult().Length; i++)
                eps = Math.Max(eps, GetRelativeError(Math.Abs(GetResult()[i] - previousIterationResult[i]), GetResult()[i]));
            return eps;
        }

        /// <summary>
        /// Выполняет итерацию
        /// </summary>
        /// <returns></returns>
        public override double DoIteration()
        {
            try
            {
                Vector start = new Vector(GetResult());
                int n = _projections.Length;
                for (int i = 0; i < n; i++)
                {
                    _projections[i].SetOrigin(GetResult());
                    _unidimensionalFinder.Function = _projections[i];
                    _unidimensionalFinder.InitialBestZeroApproximation = 0;
                    _unidimensionalFinder.Evaluate();
                    SetInitialValue(_projections[i].GetOriginShiftedBy(_unidimensionalFinder.BestZeroApproximation).CloneComponents());
                }
                RotateDirections();
                AdjustLastDirection(start);
                return ComputeRelativeError(start.CloneComponents());
            }
            catch (OverflowException)
            {
                return double.NaN;
            }
            catch (BIMatrixIllegalDimensionException)
            {
                return double.NaN;
            };
        }

        /// <summary>
        /// Создает случайную популяцию
        /// </summary>
        public override void CreateRandomPopulation()
        {
            _projections = new VectorProjectedFunction[GetResult().Length];
            double[] v = new double[GetResult().Length];
            for (int i = 0; i < _projections.Length; i++)
            {
                v[i] = 1;
                _projections[i] = new VectorProjectedFunction(Function, GetResult(), v);
                v[i] = 0;
            }
            _unidimensionalFinder = new OneVariableOptimizer(_projections[0], PointsFactory);
            _unidimensionalFinder.SetDesiredPrecision(DesiredPrecision);
        }

        /// <summary>
        /// Вращает направления
        /// </summary>
        private void RotateDirections()
        {
            Vector firstDirection = _projections[0].Direction;
            int n = _projections.Length;
            for (int i = 1; i < n; i++)
                _projections[i - 1].Direction = _projections[i].Direction;
            _projections[n - 1].Direction = firstDirection;
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(
                string.Format("{0} iterations, precision = {1}\nResult:", this.IterationsCount, this.RealPrecision ) );
            for (int i = 0; i < GetResult().Length; i++)
            {
                sb.Append(' ');
                sb.Append(GetResult()[i]);
            }
            for (int i = 0; i < _projections.Length; i++)
            {
                sb.Append('\n');
                sb.Append(_projections[i]);
            }
            return sb.ToString();
        }
    }
}
